#include "src\precompiled.h"
#include "src\scene_saver.h"
#include "src\mesh.h"
#include "src\model.h"
#include "src\material.h"
#include "src\texture.h"
#include "src\string_converter.h"
#include "src\file_system.h"
#include "src\animation.h"
#include "src\skeleton.h"
#include "src\dialog.h"

enum
{
	PRS_TRANSLATION,
	PRS_ROTATION,
	PRS_SCALE
};

static inline std::string get_unique_id(const std::string &id, std::set<std::string> &exists)
{
	std::string temp = id;
	for(int i = 1;;i++) {
		std::set<std::string>::iterator it = exists.find(temp);
		if(it == exists.end()) {
			exists.insert(temp);
			return temp;
		}
		temp = id + std::string("_duplicate_") + string_converter::to_string(i);
	}
}

static inline std::string fix_name(const std::string &source)
{
	std::string temp = string_converter::to_lower_case(source);
	temp = string_converter::replace(temp, " ", "_");
	temp = string_converter::replace(temp, "/", "_");
	temp = string_converter::replace(temp, "\\", "_");
	temp = string_converter::replace(temp, ".", "_");
	temp = string_converter::replace(temp, ":", "_");
	temp = string_converter::replace(temp, ";", "_");
	temp = string_converter::replace(temp, ",", "_");
	temp = string_converter::replace(temp, "?", "_");
	temp = string_converter::replace(temp, "=", "_");
	temp = string_converter::replace(temp, "+", "_");
	temp = string_converter::replace(temp, "^", "_");
	temp = string_converter::replace(temp, "%", "_");
	temp = string_converter::replace(temp, "(", "_");
	temp = string_converter::replace(temp, ")", "_");
	temp = string_converter::replace(temp, "[", "_");
	temp = string_converter::replace(temp, "]", "_");
	return temp;
}

bool operator != (const ILinFloatKey &k1, const ILinFloatKey &k2) { return (k1.val != k2.val); }
bool operator != (const ILinPoint3Key &k1, const ILinPoint3Key &k2) { return (k1.val != k2.val); }
bool operator != (const ILinRotKey &k1, const ILinRotKey &k2) { return !(k1.val == k2.val); }
bool operator != (const ILinScaleKey &k1, const ILinScaleKey &k2) { return (k1.val.s != k2.val.s); }

bool operator != (const IBezFloatKey &k1, const IBezFloatKey &k2) { return (k1.val != k2.val); }
bool operator != (const IBezPoint3Key &k1, const IBezPoint3Key &k2) { return (k1.val != k2.val); }
bool operator != (const IBezQuatKey &k1, const IBezQuatKey &k2) { return !(k1.val == k2.val); }
bool operator != (const IBezScaleKey &k1, const IBezScaleKey &k2) { return (k1.val.s != k2.val.s); }

bool operator != (const ITCBFloatKey &k1, const ITCBFloatKey &k2) { return (k1.val != k2.val); }
bool operator != (const ITCBPoint3Key &k1, const ITCBPoint3Key &k2) { return (k1.val != k2.val); }
bool operator != (const ITCBRotKey &k1, const ITCBRotKey &k2) { return !((k1.val.angle == k2.val.angle) && (k1.val.axis == k2.val.axis)); }
bool operator != (const ITCBScaleKey &k1, const ITCBScaleKey &k2) { return (k1.val.s != k2.val.s); }

template <class T>
static inline bool is_animated(Control *control)
{
	bool animated = false;
	if(control == 0)
		return animated;
	if(control->IsAnimated() == FALSE)
		return animated;
	if(control->NumKeys() < 2)
		return animated;

	T key, lastkey;
	IKeyControl *kc = GetKeyControlInterface(control);
	for(int i = 0; i < kc->GetNumKeys() && !animated; i++) {
		kc->GetKey(i, &key);
		if(i > 0 && lastkey != key)
			animated = true;
		lastkey = key;
	}

	return animated;
}

template <class T>
static inline std::vector<T> extract_animation_keys(Control *control)
{
	std::vector<T> keys;
	IKeyControl *kc = GetKeyControlInterface(control);
	if(kc != 0) {
		for(int i = 0; i < kc->GetNumKeys(); i++) {
			T key;
			kc->GetKey(i, &key);
			keys.push_back(key);
		}
	}

	return keys;
}

static inline bool extract_node_animation_track(Control *controller, node_animation_track &track)
{
	if(controller == 0)
		return false;

	Class_ID clsid = controller->ClassID();
	if(clsid.PartB() != 0)
		return false;

	bool animated = false;
	switch(clsid.PartA())
	{
	case LININTERP_POSITION_CLASS_ID:
		animated = is_animated<ILinPoint3Key>(controller);
		if(animated) {
			std::vector<ILinPoint3Key> keys = extract_animation_keys<ILinPoint3Key>(controller);
			track.set_key_values(keys);
		}
		return animated;
	case LININTERP_ROTATION_CLASS_ID:
		animated = is_animated<ILinRotKey>(controller);
		if(animated) {
			std::vector<ILinRotKey> keys = extract_animation_keys<ILinRotKey>(controller);
			track.set_key_values(keys);
		}
		return animated;
	case LININTERP_SCALE_CLASS_ID:
		animated = is_animated<ILinScaleKey>(controller);
		if(animated) {
			std::vector<ILinScaleKey> keys = extract_animation_keys<ILinScaleKey>(controller);
			track.set_key_values(keys);
		}
		return animated;
	case HYBRIDINTERP_POINT3_CLASS_ID:
	case HYBRIDINTERP_POSITION_CLASS_ID:
		animated = is_animated<IBezPoint3Key>(controller);
		if(animated) {
			std::vector<IBezPoint3Key> keys = extract_animation_keys<IBezPoint3Key>(controller);
			track.set_key_values(keys);
		}
		return animated;
	case HYBRIDINTERP_ROTATION_CLASS_ID:
		animated = is_animated<IBezQuatKey>(controller);
		if(animated) {
			std::vector<IBezQuatKey> keys = extract_animation_keys<IBezQuatKey>(controller);
			track.set_key_values(keys);
		}
		return animated;
	case HYBRIDINTERP_SCALE_CLASS_ID:
		animated = is_animated<IBezScaleKey>(controller);
		if(animated) {
			std::vector<IBezScaleKey> keys = extract_animation_keys<IBezScaleKey>(controller);
			track.set_key_values(keys);
		}
		return animated;
	case TCBINTERP_POSITION_CLASS_ID:
		animated = is_animated<ITCBPoint3Key>(controller);
		if(animated) {
			std::vector<ITCBPoint3Key> keys = extract_animation_keys<ITCBPoint3Key>(controller);
			track.set_key_values(keys);
		}
		return animated;
	case TCBINTERP_ROTATION_CLASS_ID:
		animated = is_animated<ITCBRotKey>(controller);
		if(animated) {
			std::vector<ITCBRotKey> keys = extract_animation_keys<ITCBRotKey>(controller);
			track.set_key_values(keys);
		}
		return animated;
	case TCBINTERP_SCALE_CLASS_ID:
		animated = is_animated<ITCBScaleKey>(controller);
		if(animated) {
			std::vector<ITCBScaleKey> keys = extract_animation_keys<ITCBScaleKey>(controller);
			track.set_key_values(keys);
		}
		return animated;
	default:
		return false;
	}
}

static inline bool extract_node_animation_track(Control *controller, node_animation_track &track, int prs)
{
	if(controller == 0)
		return false;

	if(extract_node_animation_track(controller, track))
		return true;

	Control *cx = controller->GetXController();
	Control *cy = controller->GetYController();
	Control *cz = controller->GetZController();
	Control *xyz[3] = {cx, cy, cz};

	bool animated[3];

	for(int i = 0; i < 3; i++) {
		Control *ctl = xyz[i];
		if(ctl == 0) {
			animated[i] = false;
			continue;
		}

		Class_ID clsid = ctl->ClassID();
		if(clsid.PartB() != 0) {
			animated[i] = false;
			continue;
		}

		switch(clsid.PartA())
		{
		case LININTERP_FLOAT_CLASS_ID:
			animated[i] = is_animated<ILinFloatKey>(ctl);
			if(animated[i]) {
				std::vector<ILinFloatKey> keys = extract_animation_keys<ILinFloatKey>(ctl);
				track.set_key_values(prs, i, keys);
			}
			break;
		case HYBRIDINTERP_FLOAT_CLASS_ID:
			animated[i] = is_animated<IBezFloatKey>(ctl);
			if(animated[i]) {
				std::vector<IBezFloatKey> keys = extract_animation_keys<IBezFloatKey>(ctl);
				track.set_key_values(prs, i, keys);
			}
			break;
		case TCBINTERP_FLOAT_CLASS_ID:
			animated[i] = is_animated<ITCBFloatKey>(ctl);
			if(animated[i]) {
				std::vector<ITCBFloatKey> keys = extract_animation_keys<ITCBFloatKey>(ctl);
				track.set_key_values(prs, i, keys);
			}
			break;
		default:
			animated[i] = false;
			break;
		}
	}

	if(!animated[0] && !animated[1] && !animated[2])
			return false;

	return true;
}

static inline bool extract_node_animation_track(INode *node, node_animation_track &track)
{
	Control *tmc = node->GetTMController();
	bool is_tm_animated = (tmc != 0 && tmc->IsAnimated() == TRUE) ? true : false;
	if(!is_tm_animated)
		false;

	Control *pc = tmc->GetPositionController();
	Control *rc = tmc->GetRotationController();
	Control *sc = tmc->GetScaleController();

	bool is_translation_animated = extract_node_animation_track(pc, track, PRS_TRANSLATION);
	bool is_rotation_animated = extract_node_animation_track(rc, track, PRS_ROTATION);
	bool is_scale_animated = extract_node_animation_track(sc, track, PRS_SCALE);

	return (is_translation_animated || is_rotation_animated || is_scale_animated);
}

static inline Modifier *find_modifier(INode *node, Class_ID clsid)
{
	Object *obj = node->GetObjectRef();
	while(obj != 0 && obj->SuperClassID() == GEN_DERIVOB_CLASS_ID) {
		IDerivedObject* derived = static_cast<IDerivedObject*>(obj);
		for(int i = 0; i < derived->NumModifiers(); i++) {
			Modifier *mod = derived->GetModifier(i);
			if(mod->ClassID() == clsid)
				return mod;
		}
		obj = derived->GetObjRef();
	}
	return 0;
}

static inline matrix4 to_matrix4(const Matrix3 &tm)
{
	return matrix4(
		tm.GetRow(0)[0], tm.GetRow(1)[0], tm.GetRow(2)[0], tm.GetRow(3)[0],
		tm.GetRow(0)[1], tm.GetRow(1)[1], tm.GetRow(2)[1], tm.GetRow(3)[1],
		tm.GetRow(0)[2], tm.GetRow(1)[2], tm.GetRow(2)[2], tm.GetRow(3)[2],
			0.0f,				0.0f,			0.0f,			1.0f
		);
}

static inline matrix4 get_node_transforms(INode *node, TimeValue time)
{
	Matrix3 tm;
	if(node->GetParentNode() == GetCOREInterface()->GetRootNode())
		tm = node->GetNodeTM(time);
	else {
		Matrix3 pm = node->GetParentTM(time);
		Matrix3 nm = node->GetNodeTM(time);
		pm.Invert();
		tm = nm * pm;
	}

	return to_matrix4(tm);
};

static inline matrix4 get_local_node_transforms(INode *node, TimeValue time)
{
	Matrix3 tm;
	if(node->GetParentNode() == GetCOREInterface()->GetRootNode())
		return matrix4();
	else {
		Matrix3 pm = node->GetParentTM(time);
		Matrix3 nm = node->GetNodeTM(time);
		pm.Invert();
		tm = nm * pm;
	}

	return to_matrix4(tm);
};

template<>
static mesh_vec::iterator std::find(mesh_vec::iterator first, mesh_vec::iterator last, const mesh_ptr &mesh)
{
	for(mesh_vec::iterator it = first; it != last; it++) {
		if(mesh->get_num_vertices() != (*it)->get_num_vertices())
			continue;
		if(mesh->get_bones().size() != (*it)->get_bones().size())
			continue;
		for(size_t j = 0; j < mesh->get_num_vertices(); j++) {
			if(mesh->get_coord(j) != (*it)->get_coord(j))
				return last;
		}
		return it;
	}
	return last;
}

static inline bool is_exportable(INode *node)
{
	Object *obj = node->EvalWorldState(0).obj;
	if(obj == 0)
		return false;

	Class_ID clsid = obj->ClassID();
	SClass_ID sclsid = obj->SuperClassID();

	if(clsid == SKELOBJ_CLASS_ID)
		return false;
	else if(clsid == BONE_OBJ_CLASSID)
		return false;
	else if(clsid.PartA() == BONE_CLASS_ID)
		return false;
	else if(sclsid == CAMERA_CLASS_ID)
		return false;
	else if(sclsid == LIGHT_CLASS_ID)
		return false;
	else if(sclsid == SHAPE_CLASS_ID)
		return false;
	else if(sclsid == MATERIAL_CLASS_ID)
		return false;
	return true;
}

static inline Point3 extract_normal(Mesh * m, int f, int v, int i)
{
	RVertex * rv = m->getRVertPtr(i);
	if(rv != 0) {
		if(rv->rFlags & SPECIFIED_NORMAL)
			return rv->rn.getNormal();

		int sg = m->faces[f].smGroup;
		int count = rv->rFlags & NORCT_MASK;
		if(count == 1)
			return rv->rn.getNormal();
		if(count && sg) {
			for(int i = 0; i < count; i++) {
				if(rv->ern[i].getSmGroup() & sg)
					return rv->ern[i].getNormal();
			}
		}
	}
	return m->getFaceNormal(f);
}

//-----------------------------------------------------------------------------

scene_saver::scene_saver(TimeValue time, bool export_selected)
	:	m_export_selected(export_selected),
		m_time(time)
{ }

scene_saver::~scene_saver() { }

void scene_saver::clear()
{
	m_models.clear();
	m_meshes.clear();
	m_materials.clear();
	m_textures.clear();
	m_meshobj.clear();
	m_mtls.clear();
	m_tex.clear();
	m_matids.clear();
	m_meshids.clear();
	m_modelids.clear();
	m_animids.clear();
	m_sklids.clear();
	m_skeletons.clear();
	m_jntids.clear();
}

int scene_saver::callback(INode *node)
{
	const int ret = TREE_IGNORECHILDREN;

	if(node->IsObjectHidden())
		return ret;
	else if(m_export_selected == TRUE && node->Selected() == FALSE)
		return ret;
	else if(!is_exportable(node))
		return ret;
	else {
		model_ptr mdl = extract_model(node);
		if(!mdl.is_null())
			m_models.push_back(mdl);
	}
	return ret;
}

model_ptr scene_saver::extract_model(INode *node)
{
	if(!is_exportable(node))
		return model_ptr();

	mesh_vec meshes = extract_meshes(node);
	material_vec materials = extract_materials(node->GetMtl());

	mesh_vec temp;
	for(size_t i = 0; i < meshes.size(); i++)
		temp.push_back(meshes[i].clone());

	optimize(temp, materials);

	model_ptr mdl(new model);

	if(dialog::get_singleton().export_skeleton()) {
		skeleton_ptr skl = extract_skeletal_animation(node, temp);
		if(!skl.is_null()) {
				skeleton_vec::iterator it = std::find(m_skeletons.begin(), m_skeletons.end(), skl);
		if(it == m_skeletons.end())
				m_skeletons.push_back(skl);
			mdl->set_skeletal_animation(skl);
		}
	}

	for(size_t i = 0; i < temp.size(); i++) {
		mesh_vec::iterator it = std::find(m_meshes.begin(), m_meshes.end(), temp[i]);
		if(it != m_meshes.end())
			temp[i] = (*it);
		else {
			std::string name = get_unique_id(fix_name(std::string(node->GetName())), m_meshids);
			temp[i]->set_name(name);
			m_meshes.push_back(temp[i]);
		}
	}

	mdl->set_meshes(temp);
	mdl->set_materials(materials);

	for(size_t i = 0; i < node->NumChildren(); i++) {
		model_ptr submdl = extract_model(node->GetChildNode(i));
		if(!submdl.is_null())
			mdl->add_child(submdl);
	}

	if(mdl->is_empty())
		return model_ptr();

	std::string name = get_unique_id(fix_name(node->GetName()), m_modelids);
	mdl->set_name(name);

	for(size_t i = 0; i < materials.size(); i++) {
		material_vec::iterator it = std::find(m_materials.begin(), m_materials.end(), materials[i]);
		if(it == m_materials.end())
			m_materials.push_back(materials[i]);
	}

	matrix4 matrix;
	if(dialog::get_singleton().scene_export_option() == SO_EXPORT_LOCAL_TM)
		matrix = get_local_node_transforms(node, m_time);
	else
		matrix = get_node_transforms(node, m_time);
	mdl->set_transforms(matrix);

	if(dialog::get_singleton().export_animations())
		mdl->set_node_animation(extract_node_animation(node, matrix));

	return mdl;
}

mesh_ptr scene_saver::extract_mesh(size_t id, Mesh *m, const Matrix3 &tm)
{
	m->checkNormals(TRUE);
	
 	int nf = m->getNumFaces();
	mesh_ptr md(new mesh("untitled"));

	std::map<int, int> vindex_map;
	std::map<int, int> tindex_map;
	for(int i = 0; i < nf; i++) {
		for(int j = 0; j < 3; j++) {
			Face f = m->faces[i];
			if(f.getMatID() != id)
				continue;

			DWORD vid = f.v[j];
			DWORD tid = vid;
			if(m->numTVerts > 0)
				tid = m->tvFace[i].t[j];

			Point3 v = m->verts[vid] * tm;
			Point3 n = extract_normal(m, i, j, vid);
			Point3 uvw;
			if(m->numTVerts > 0)
				uvw = m->tVerts[tid];

			DWORD index;
			std::map<int, int>::iterator it1 = vindex_map.find(vid);
			std::map<int, int>::iterator it2 = tindex_map.find(tid);
			if(it1 != vindex_map.end() && it2 != tindex_map.end() && it1->second == it2->second)
				index = it1->second;
			else {
				index = md->get_num_vertices();
				vindex_map[vid] = index;
				tindex_map[tid] = index;
				md->set_coord(index, v.x, v.y, v.z);
				md->set_normal(index, n.x, n.y, n.z);
				md->set_texcoord(index, uvw.x, 1-uvw.y);
				md->set_vertex_binding(index, vid);
			}
			md->add_index(index);
		}
	}

	md->set_mat_id(id);
	return md;
}

mesh_vec scene_saver::extract_meshes(INode *node)
{
	Object *obj = node->EvalWorldState(m_time).obj;
	mesh_map::iterator it = m_meshobj.find(obj);
	if(it != m_meshobj.end())
		return  it->second;
	mesh_vec meshes;
	if(obj->CanConvertToType(triObjectClassID) && obj->SuperClassID() == GEOMOBJECT_CLASS_ID) {
		TriObject *triobj = (TriObject*) obj->ConvertToType(m_time, triObjectClassID);
		Mesh *mesh = &triobj->GetMesh();
		if(mesh != NULL) {
			std::set<size_t> material_ids;
			for(int i = 0; i < mesh->numFaces; i++) {
				Face f = mesh->faces[i];
				size_t id = f.getMatID();
				material_ids.insert(id);
			}
			int id = 0;
			Matrix3 inv = node->GetNodeTM(m_time);
			inv.Invert();
			Matrix3 tm = node->GetObjectTM(m_time) * inv;
			std::set<size_t>::iterator it;
			for(it = material_ids.begin(); it != material_ids.end(); it++) {
				mesh_ptr m = extract_mesh(*it, mesh, tm);
				meshes.push_back(m);
			}
		}
	}
	m_meshobj[obj] = meshes;
	return meshes;
}

material_vec scene_saver::extract_materials(Mtl *m)
{
	if(m == 0)
		return material_vec();
	mtl_map::iterator it2 = m_mtls.find(m);
	if(it2 != m_mtls.end())
		return it2->second;
	material_vec materials;
	if (m->ClassID() == Class_ID(DMTL_CLASS_ID, 0)) {
		material_ptr mtl(new material());
		std::string name = get_unique_id(fix_name(std::string(m->GetName())), m_matids);
		mtl->set_name(name);

		Color c = m->GetAmbient();
		mtl->set_ambbient_colour(vector4(c.r, c.g, c.b, 0.0f));
		c = m->GetDiffuse();
		mtl->set_diffuse_colour(vector4(c.r, c.g, c.b, 1.0f));
		c = m->GetSpecular();
		mtl->set_specular_colour(vector4(c.r, c.g, c.b, 1.0f));

		StdMat *mat = (StdMat*) m;
		mtl->set_doublesided(mat->GetTwoSided() == TRUE);
		materials.push_back(mtl);

		Texmap *texmap = mat->GetSubTexmap(ID_DI);
		if (texmap != 0 && texmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
			BitmapTex *bmt = (BitmapTex*) texmap;
			Texmap *opacity = mat->GetSubTexmap(ID_OP);
			if(opacity != 0 && opacity == texmap)
				mtl->set_transparency(true);
			texture_ptr map = extract_texture(bmt);
			if(!map.is_null())
				mtl->set_diffuse_map(map);
		}
	}
	else if(m->ClassID() == Class_ID(MULTI_CLASS_ID, 0)) {
		int nsubs = m->NumSubMtls();
		for(int i = 0; i < nsubs; i++) {
			Mtl *sm = m->GetSubMtl(i);
			material_vec submaterials = extract_materials(sm);
			if(!submaterials.empty())
				materials.insert(materials.end(),
					submaterials.begin(), submaterials.end());
		}
	}
	m_mtls[m] = materials;
	return materials;
}

texture_ptr scene_saver::extract_texture(BitmapTex *bmt)
{
	Bitmap *bm = bmt->GetBitmap(m_time);
	if(bm == 0)
		return texture_ptr();
	texture_map::iterator it = m_tex.find(bm);
	if(it != m_tex.end())
		return it->second;
	std::string name = file_system::get_singleton().parse_filename(std::string(bmt->GetMapName()));
	bool has_alpha = (bm->HasAlpha() == TRUE);
	Matrix3 tm;
	bmt->GetUVTransform(tm);
	matrix4 uvtm = to_matrix4(tm);
	matrix4 conv(
		0.0, 1.0, 0.0, 0.0,
		1.0, 0.0, 0.0, 0.0,
		0.0, 0.0, 1.0, 0.0,
		0.0, 0.0, 0.0, 1.0
		);
	uvtm = conv * uvtm * conv.inverse();
	texture_ptr t(new texture(name, bm, uvtm, has_alpha));
	m_tex[bm] = t;
	m_textures.push_back(t);
	return t;
}

animation_ptr scene_saver::extract_node_animation(INode *node, const matrix4 &tm)
{
	node_animation_track track;
	bool is_animated = extract_node_animation_track(node, track);
	if(!is_animated)
		return animation_ptr();

	track.apply_initial_transforms(tm);

	animation_ptr anim = animation_ptr(new animation);
	std::string name = fix_name(std::string(node->GetName()) + std::string("_node_animation"));
	name = get_unique_id(name, m_animids);
	anim->set_name(name);
	anim->add_node_animation_track(track);

	return anim;
}

skeleton_ptr scene_saver::extract_skeletal_animation(INode *node, mesh_vec &meshes)
{
	Modifier *skin_mod = find_modifier(node, SKIN_CLASSID);
	if(skin_mod == 0)
		return skeleton_ptr();

	ISkin *skin = (ISkin*) skin_mod->GetInterface(I_SKIN);
	ISkinContextData *data = skin->GetContextInterface(node);

	skeleton_ptr skl;
	Object *obj = node->EvalWorldState(m_time).obj;
	skeleton_map::iterator it = m_skls.find(obj);
	if(it == m_skls.end()) {	
		// fill our skeleton with bones & animations
		skeleton s;
		animation anim;
		std::map<INode*, size_t> node_map;
		for(int i = 0; i < skin->GetNumBones(); i++) {
			INode *bone = skin->GetBone(i);
			INode *parent = bone->GetParentNode();

			Matrix3 bm, pm, tm, nm, dm;
			bm = bone->GetNodeTM(0);
			pm = parent->GetNodeTM(0);
			nm = node->GetNodeTM(0);
			bool is_root;
			if(is_root = skin->GetBoneInitTM(parent, dm) == SKIN_INVALID_NODE_PTR)
				pm = nm;
			tm = bm * Inverse(pm);
			
			joint_ptr jnt = s.create_joint(i);
			jnt->set_transforms(to_matrix4(tm));
	
			std::string name = fix_name(std::string(bone->GetName()));
			name = get_unique_id(name, m_jntids);
			jnt->set_name(name);
	
			node_map[bone] = i;
			
			Interval range = GetCOREInterface()->GetAnimRange();
			std::vector<int> times;
			for(int j = range.Start(); j < range.End(); j += GetTicksPerFrame())
				times.push_back(j);
			times.push_back(range.End());
			
			node_animation_track track;
			Matrix3 bind = bone->GetNodeTM(m_time) * Inverse(parent->GetNodeTM(m_time));
			for(size_t j = 0; j < times.size(); j++) {
				bm = bone->GetNodeTM(times[j]);
				pm = parent->GetNodeTM(times[j]);
				Matrix3 m;
				dm = bm * Inverse(pm);
				m = dm * Inverse(bind);
				matrix4 m4 = to_matrix4(m);
				track.add_key(times[j], m4);
			}

			anim.add_node_animation_track(track);
		}
	
		animation_ptr anm(new animation(anim));
		std::string name = fix_name(std::string(node->GetName()) + std::string("_skeletal_animation"));
		name = get_unique_id(name, m_animids);
		anm->set_name(name);
		s.set_animation(anm);
	
		// build hierarchy
		for(int i = 0; i < skin->GetNumBones(); i++) {
			INode *bone = skin->GetBone(i);
			INode *parent = bone->GetParentNode();
			std::map<INode*, size_t>::iterator it = node_map.find(parent);
			if(it != node_map.end()) {
				int parent_id = it->second;
				joint_ptr jnt = s.get_joint_by_id(i);
				joint_ptr parent_jnt = s.get_joint_by_id(parent_id);
				parent_jnt->add_child(jnt.ptr());
			}
		}
	
		// assign unique name
		name = fix_name(std::string(node->GetName()));
		name = get_unique_id(name, m_sklids);
		s.set_name(name);
	
		skl = skeleton_ptr(new skeleton(s));
		m_skls[obj] = skl;
	}
	else
		skl = it->second;

	// assign bone weights to vertices
	for(size_t i = 0; i < meshes.size(); i++) {
		mesh_ptr m = meshes[i];
		m->set_skeleton(skl);
		for(size_t j = 0; j < m->get_num_vertices(); j++) {
			size_t vert = m->get_vertex_binding(j);
			for(int k = 0; k < data->GetNumAssignedBones(vert); k++) {
				int idx = data->GetAssignedBone(vert, k);
				if(idx < 0)
					m->set_bone_weight(j, k, 0, 0.0f);
				else {
					float weight = data->GetBoneWeight(vert, k);
					m->set_bone_weight(j, k, idx, weight);
				}
			}
		}
	}

	skin_mod->ReleaseInterface(I_SKIN, skin);

	return skl;
}

void scene_saver::optimize(mesh_vec &meshes, material_vec &materials)
{
	if(meshes.size() == 0 || (materials.size() == 1 && meshes.size() == 1))
		return;

	if(materials.size() == 0) {
		mesh_ptr first = meshes[0];
		for(size_t i = 1; i < meshes.size(); i++) {
			mesh_ptr next = meshes[i];
			first->merge(*next);
		}
		meshes.clear();
		meshes.push_back(first);
	}
	else {
		material_vec temp(materials.begin(), materials.end());
		materials.clear();
		for(size_t i = 0; i < meshes.size(); i++) {
			int idx = meshes[i]->get_mat_id() % temp.size();
			materials.push_back(temp[idx]);
		}
	}

	std::vector<std::vector<int>> duplicates;
	std::list<int> material_ids;
	for(size_t i = 0; i < materials.size(); i++)
		material_ids.push_back(i);

	std::list<int>::iterator it1, it2, it3;
	for(it1 = material_ids.begin(); it1 != material_ids.end(); it1++) {
		it2 = it1; it2++;
		bool first = true;
		bool exists = false;
		while(it2 != material_ids.end()) {
			material_ptr m1 = materials[*it1];
			material_ptr m2 = materials[*it2];
			if(m1 == m2) {
				exists = true;
				if(first) {
					duplicates.resize(duplicates.size() + 1);
					duplicates.back().push_back(*it1);
					first = false;
				}
				duplicates.back().push_back(*it2);
				it3 = it2++;
				material_ids.erase(it3);
			}
			else
				it2++;
		}
		if(!exists) {
			duplicates.resize(duplicates.size() + 1);
			duplicates.back().push_back(*it1);
		}
	}

	std::vector<mesh_ptr> _meshes;
	std::vector<material_ptr> _materials;
	for(size_t i = 0; i < duplicates.size(); i++) {
		int id = duplicates[i][0];
		mesh_ptr first = meshes[id];
		_meshes.push_back(meshes[id]);
		_materials.push_back(materials[id]);

		for(size_t j = 1; j < duplicates[i].size(); j++) {
			int offset = duplicates[i][j];
			mesh_ptr msh = meshes[offset];
			first->merge(*msh);
			material_ptr mat = materials[offset];
		}
	}

	if(!duplicates.empty()) {
		meshes = _meshes;
		materials = _materials;
	}
}